在上一篇文章中,我們看清了殘酷的現實:如果讓系統中的各個執行緒 (Threads) 各自為政、直接操作 GPIO 控制 LED 燈號,最終只會演變成一場資源爭奪的災難。互斥鎖 (Lock) 雖然能防止混色,卻會導致重要的警告燈號被低優先級的動畫給阻塞。
為了解決這個問題,我們將引入「軟即時 (Soft Real-Time)」系統的概念。
在純硬體的即時作業系統 (RTOS) 中,系統保證高優先級的任務能夠在微秒級別的時間內「搶佔 (Preempt)」低優先級的任務。Linux 雖然不是即時作業系統,但我們可以在 Python 應用程式內部,實作一個類似的排程機制。
我們的核心理念是:解耦與集權。
將原本分散在各處的 LED 控制邏輯拔除,統一交由一個獨立的 LEDController 執行緒來管理。
這就像一個交通十字路口,各個執行緒就像是一輛輛汽車,而 LEDController 就是那個站在路中間指揮交通的警察。
我們需要一個狀態機機制,讓這個唯一的交通警察能夠評估當前的局勢,並決定現在應該亮什麼燈。
我們不再只是單純地給予一個顏色,而是將每一種燈號行為定義為一個動畫函式 (Animation Function)。
# animations.py
import time
def breathe_green(led_hardware, check_cancel):
while not check_cancel():
# 實作綠燈漸亮
led_hardware.set_pwm(GREEN, intensity)
time.sleep(0.05)
# 實作綠燈漸暗...
def blink_blue(led_hardware, check_cancel):
while not check_cancel():
led_hardware.set_color(BLUE)
time.sleep(0.5)
if check_cancel(): break
led_hardware.set_color(BLACK)
time.sleep(0.5)
[!IMPORTANT]
注意到這裡多了一個神秘的參數:check_cancel。這是一個回呼函式 (Callback),每次迴圈或睡眠後,動畫都必須呼叫它。如果它回傳True,動畫就必須立刻終止break。這是我們實現「搶佔」的基石!
接下來,我們建立這位交通警察。他擁有一個內部的佇列 (Queue) 或者直接記錄當前的「請求」。
# led_controller.py
import threading
class LEDController:
def __init__(self, hardware):
self.hw = hardware
self._current_anim_thread = None
self._cancel_flag = False
def _run_anim(self, anim_func):
# 將檢查旗標的函式傳給動畫
anim_func(self.hw, lambda: self._cancel_flag)
def request_animation(self, anim_func):
# 1. 停止目前的動畫 (發送中斷訊號)
if self._current_anim_thread and self._current_anim_thread.is_alive():
self._cancel_flag = True
self._current_anim_thread.join() # 等待舊動畫乖乖結束
# 2. 重置中斷旗標,準備啟動新動畫
self._cancel_flag = False
# 3. 啟動新動畫
self._current_anim_thread = threading.Thread(
target=self._run_anim,
args=(anim_func,)
)
self._current_anim_thread.start()
現在,其他執行緒再也不需要直接碰觸硬體了。當 SyncEngine 發現有新資料傳入時,它只需要做一件事:
# 在 SyncEngine 內部
led_controller.request_animation(blink_blue)
發生了什麼事?
LEDController 收到請求。breathe_green。_cancel_flag 設為 True。breathe_green 醒來,檢查 check_cancel() 發現變為 True,立刻 break 退出。LEDController 隨即啟動 blink_blue 動畫。這就是我們的軟體搶佔機制!
沒有混色、沒有互相踩踏。新的指令總是能迅速且乾淨地取代舊的指令。
但等等,這個架構還缺了一塊非常致命的拼圖。如果現在正在「閃紅燈(致命錯誤)」,而有一個不知好歹的背景執行緒突然發送了「待機綠燈」的請求,紅燈不就被綠燈搶佔覆蓋了嗎!?
沒錯,交通警察需要能夠分辨「長官座車」與「一般民車」的能力。明天,我們將在 Day 22 探討如何為這套架構加入優先權設計 (Priority Design) 與世代計數器 (Generation Counter),徹底完善這個系統!